import React, { useRef, useState, useEffect, useContext, createContext, createElement, PropsWithChildren } from 'react';
import ReactDom from 'react-dom';

interface ElementMap {
  [key: string]: {
    id: string;
    element: JSX.Element;
    nodes?: HTMLElement[];
  };
}

interface KeepAliveContextProps {
  elements: ElementMap;
  onPushKeepAlive: (id: string, element: JSX.Element) => void;
}

const KeepAliveContext = createContext<KeepAliveContextProps>({} as KeepAliveContextProps);

/** 用于创建keepAlive组件的方法 */
function createKeepAliveElement(id: string, element: JSX.Element) {
  return function KeepAliveComponent(props: object) {
    const { elements, onPushKeepAlive } = useContext(KeepAliveContext);
    const keepAliveRef = useRef<HTMLDivElement>(null);

    // 每次props发生变化，重新渲染组件
    useEffect(() => {
      onPushKeepAlive(
        id,
        createElement(element.type, props),
      );
    }, [props]);

    // 监听组件状态变化
    useEffect(() => {
      // 如果nodes存在，移动到当前组件的根节点下
      if (elements[id]?.nodes) {
        const keepAliveDom = keepAliveRef.current;
        elements[id]?.nodes?.forEach(node => {
          keepAliveDom?.appendChild(node);
        });
      }
    }, [elements]);

    return <div ref={keepAliveRef} style={{ display: 'contents' }} />;
  };
}

type KeepAliveProviderProps = PropsWithChildren<{children: JSX.Element }>

export function KeepAliveProvider({ children }: KeepAliveProviderProps) {
  /**
   * 存储需要借鸡生蛋的组件，存储结构如下
   * {
   *  [唯一标识]: {
   *    id:    唯一标识
   *    element: 存储jsx,
   *    nodes:  存储真实的dom
   *  }
   * }
   */
  const [elements, setElements] = useState<ElementMap>({});

  // 通过此方法添加keepAlive组件
  const onPushKeepAlive = (id: string, element: JSX.Element) => {
    const newElements = { ...elements };
    newElements[id] = { ...elements[id], id, element };
    setElements(newElements);
  };

  return (
    <KeepAliveContext.Provider value={{ elements, onPushKeepAlive }}>
      {children}
      {/* 借鸡生蛋的地方 */}
      {Object.values(elements).map(({ id, element }) => (
        <div
          key={id}
          style={{ display: 'none' }}
          ref={(dom) => {
            // 把渲染好的dom存到状态里
            if (dom && dom.children.length) {
              const newElements = { ...elements };
              newElements[id].nodes = Array.from(dom.children) as HTMLElement[];
              setElements(newElements);
            }
          }}
        >
          {element}
        </div>
      ))}
    </KeepAliveContext.Provider>
  );
}
